package log

import (
	"fmt"
	"path"
	"runtime"
	"strconv"
	"strings"
)

//ContextAllowTar Context allow target specify
type ContextAllowTar interface {
	Context

	//add a target in current log message util Z().
	Tar(t Target) ContextAllowTar
	//only use this target in current log message util Z().
	UniTar(t Target) Context
}

//Context is a status set for processing in a message outputting.
//Every message must output with context. context must flush outputting by function Z().
type Context interface {
	//clear context content
	Clear()

	//finally output function.
	Z()

	//get log level of context.
	Level() int
	//set log level to context. just available before any assemble function.
	SetLevel(level int)

	//add a temporay prefix in current log message util Z().
	Pre(prefix string) Context

	//formatted key-value message field.
	FS(key string, val string) Context  //string value
	FI(key string, val int64) Context   //integer value
	FU(key string, val uint64) Context  //unsigned integer value
	FD(key string, val float64) Context //double-float value
	FX(key string, val uint64) Context  //hex value

	//formatted key-value message custommed field.
	FT(key string, val FmtField) Context

	//add a message string.
	P(msg string) Context
	//add a message string with format string and args.
	PF(format string, args ...interface{}) Context
}

//ContextBase is a status set for processing in a message outputting.
type ContextBase struct {
	//common config info from handler
	Common *HandlerCommon

	level       int
	prefix      []string
	fmtKey      []string
	fmtVal      []interface{}
	fmtFieldKey []string
	fmtFieldVal []FmtField
	segFmt      []string
	segArgs     [][]interface{}

	targets      []Target
	uniqueTarget Target

	buff  *strings.Builder
	tmpTs []Target

	fAssemble ContextBaseAssembleFunc
	fRelease  func(ctx Context)
}

//NewContextBase make a new ContextBase instance
func NewContextBase(c *HandlerCommon, level int) *ContextBase {
	ctx := &ContextBase{
		Common:       c,
		level:        level,
		prefix:       make([]string, 0, 8),
		fmtKey:       make([]string, 0, 8),
		fmtVal:       make([]interface{}, 0, 8),
		fmtFieldKey:  make([]string, 0, 8),
		fmtFieldVal:  make([]FmtField, 0, 8),
		segFmt:       make([]string, 0, 8),
		segArgs:      make([][]interface{}, 0, 8),
		targets:      make([]Target, 0, 8),
		uniqueTarget: nil,

		buff:  new(strings.Builder),
		tmpTs: make([]Target, 0, 8),

		fAssemble: defaultAssembleFunc,
		fRelease:  nil,
	}
	ctx.buff.Grow(2048)
	return ctx
}

//Clear implement Context.Clear()
func (ctx *ContextBase) Clear() {
	ctx.level = GlobalLevel()
	ctx.prefix = ctx.prefix[0:0]
	ctx.fmtKey = ctx.fmtKey[0:0]
	ctx.fmtVal = ctx.fmtVal[0:0]
	ctx.fmtFieldKey = ctx.fmtFieldKey[0:0]
	ctx.fmtFieldVal = ctx.fmtFieldVal[0:0]
	ctx.segFmt = ctx.segFmt[0:0]
	ctx.segArgs = ctx.segArgs[0:0]
	ctx.targets = ctx.targets[0:0]
	ctx.uniqueTarget = nil
	ctx.buff.Reset()
	ctx.tmpTs = ctx.tmpTs[0:0]
}

//Level implement Context.Level()
func (ctx *ContextBase) Level() int {
	return ctx.level
}

//SetLevel implement Context.SetLevel()
func (ctx *ContextBase) SetLevel(level int) {
	ctx.level = level
}

//Pre implement Context.Pre()
func (ctx *ContextBase) Pre(prefix string) Context {
	if !ctx.targetsTmp() {
		ctx.release()
		return CtxNil
	}

	ctx.prefix = append(ctx.prefix, prefix)
	return ctx
}

//FS implement Context.FS()
func (ctx *ContextBase) FS(key string, val string) Context {
	if !ctx.targetsTmp() {
		ctx.release()
		return CtxNil
	}

	ctx.fmtKey = append(ctx.fmtKey, key)
	ctx.fmtVal = append(ctx.fmtVal, val)
	return ctx
}

//FI implement Context.FI()
func (ctx *ContextBase) FI(key string, val int64) Context {
	if !ctx.targetsTmp() {
		ctx.release()
		return CtxNil
	}

	ctx.fmtKey = append(ctx.fmtKey, key)
	ctx.fmtVal = append(ctx.fmtVal, val)
	return ctx
}

//FU implement Context.FU()
func (ctx *ContextBase) FU(key string, val uint64) Context {
	if !ctx.targetsTmp() {
		ctx.release()
		return CtxNil
	}

	ctx.fmtKey = append(ctx.fmtKey, key)
	ctx.fmtVal = append(ctx.fmtVal, val)
	return ctx
}

//FD implement Context.FD()
func (ctx *ContextBase) FD(key string, val float64) Context {
	if !ctx.targetsTmp() {
		ctx.release()
		return CtxNil
	}

	ctx.fmtKey = append(ctx.fmtKey, key)
	ctx.fmtVal = append(ctx.fmtVal, val)
	return ctx
}

//FX implement Context.FX()
func (ctx *ContextBase) FX(key string, val uint64) Context {
	if !ctx.targetsTmp() {
		ctx.release()
		return CtxNil
	}

	ctx.fmtKey = append(ctx.fmtKey, key)
	ctx.fmtVal = append(ctx.fmtVal, "0x"+strconv.FormatUint(val, 16))
	return ctx
}

//FT implement Context.FT()
func (ctx *ContextBase) FT(key string, val FmtField) Context {
	if !ctx.targetsTmp() {
		ctx.release()
		return CtxNil
	}

	ctx.fmtFieldKey = append(ctx.fmtFieldKey, key)
	ctx.fmtFieldVal = append(ctx.fmtFieldVal, val)
	return ctx
}

//P implement for Context.P()
func (ctx *ContextBase) P(msg string) Context {
	if !ctx.targetsTmp() {
		ctx.release()
		return CtxNil
	}

	ctx.segFmt = append(ctx.segFmt, msg)
	ctx.segArgs = append(ctx.segArgs, nil)
	return ctx
}

//PF implement for Context.PF()
func (ctx *ContextBase) PF(msg string, args ...interface{}) Context {
	if !ctx.targetsTmp() {
		ctx.release()
		return CtxNil
	}

	ctx.segFmt = append(ctx.segFmt, msg)
	ctx.segArgs = append(ctx.segArgs, args)
	return ctx
}

//Tar implement for Context.Tar()
func (ctx *ContextBase) Tar(t Target) ContextAllowTar {
	ctx.targets = append(ctx.targets, t)
	return ctx
}

//UniTar implement for Context.UniTar()
func (ctx *ContextBase) UniTar(t Target) Context {
	ctx.uniqueTarget = t
	if !ctx.targetsTmp() {
		ctx.release()
		return CtxNil
	}
	return ctx
}

//targetsTmp build tmp target array
func (ctx *ContextBase) targetsTmp() bool {
	if len(ctx.tmpTs) != 0 {
		return true
	}

	if nil != ctx.uniqueTarget {
		ctx.tmpTs = append(ctx.tmpTs, ctx.uniqueTarget)
	} else {
		for _, t := range ctx.Common.Targets {
			if !t.IsIgnore(ctx.level) {
				ctx.tmpTs = append(ctx.tmpTs, t)
			}
		}
		for _, t := range ctx.targets {
			if !t.IsIgnore(ctx.level) {
				ctx.tmpTs = append(ctx.tmpTs, t)
			}
		}
	}

	return len(ctx.tmpTs) != 0
}

//Z implement for Context.Z()
func (ctx *ContextBase) Z() {
	//defer release context
	defer ctx.release()

	//check level
	if LvlInvalid == ctx.level || levelIgnoreGlobal(ctx.level) {
		return
	}

	//check target ignore
	if !ctx.targetsTmp() {
		return
	}

	//assemble context to string
	str := ctx.fAssemble(ctx)

	//output
	for _, t := range ctx.tmpTs {
		t.Put(str)
	}
}

//assembleCallLine get file line of logging code
func (ctx *ContextBase) assembleCallLine() {
	_, file, line, ok := runtime.Caller(3)
	if ok {
		if !BCodeLineDetail {
			fmt.Fprintf(ctx.buff, "%s:%d", path.Base(file), line)
		} else {
			fmt.Fprintf(ctx.buff, "%s:%d", strings.TrimPrefix(file, codeLineTrimPrefix), line)
		}
	}
}

//bindRelease bind release function to ContextBase
func (ctx *ContextBase) bindRelease(f func(ctx Context)) {
	ctx.fRelease = f
}

//release call bond release function
func (ctx *ContextBase) release() {
	if nil != ctx.fRelease {
		ctx.fRelease(ctx)
	}
}
